
Planner = function(spec) {
    $.extend(this, spec);
};

Planner.prototype = {
    setPlaces: function(places) {
        this.places = $.map(places, function(place) {
            return Place.parse(place);
        });

        this.placesByItemSold = this.computePlacesByItemSold();
    },

    setTasks: function(tasks) {
        this.tasks = $.map(tasks, function(task) {
            return Task.parse(task);
        });
    },

    computePlacesByItemSold: function() {
        var map = {};
        $.each(this.places, function(_, place) {
            $.each(place.sells, function(_, item) {
                if (!map[item]) {
                    map[item] = [];
                }
                map[item].push(place);
            });
        });
        return map;
    },

    computeSingleStagePlan: function(callback) {
        var continuation = $.proxy(this.computeSingleStagePlan0, this, callback);
        this.ensurePlaceGeolocationAvailable(continuation);
    },

    computeSingleStagePlan0: function(callback) {
        var steps = $.map(this.tasks, $.proxy(function(task) {
            if (task.action == 'buy') {
                var places = this.placesByItemSold[task.item];
                if (places) {
                    var placeAndTime = this.findBestPlaceAndTime(places, task.schedule);
                    return {
                        action: task.action,
                        item: task.item,
                        time: placeAndTime.time,
                        place: placeAndTime.place
                    }
                }
            } else if (task.action == 'call') {
                return {
                    action: task.action,
                    item: task.phone,
                    time: task.schedule.earliestTime()
                };
            }
            return null;
        }, this));

        var plan = new Plan();
        plan.setSteps(steps);
        callback(plan);
    },

    ensurePlaceGeolocationAvailable: function(callback) {
        this.locationPredictor.predictLocationAtTime(new Date(), $.proxy(function(latlng) {
            this.ensurePlaceGeolocationAvailable0(latlng, callback);
        }, this));
    },

    ensurePlaceGeolocationAvailable0: function(latlng, callback) {
        var count = this.places.length;

        if (!count) {
            callback();
            return;
        }

        $.each(this.places, function(_, place) {
            place.findLocation(latlng, function() {
                count -= 1;
                if (!count) {
                    callback();
                }
            });
        });
    },

    findBestPlaceAndTime: function(places, schedule, geolocation) {
        places = $.map(places, function(place) {
            place = new Place(place);
            if (place.schedule) {
                place.schedule = place.schedule.intersect(schedule);
            }
            return place;
        });

        places = $.grep(places, function(place) {
            return !place.schedule || !place.schedule.empty();
        });

        places.sort(function(p1, p2) {
            var d1 = p1.geolocation.distance(geolocation);
            var d2 = p2.geolocation.distance(geolocation);
            return d1 - d2;
        });

        if (!places.length) {
            return null;
        }

        var place = places[0];
        var time = place.schedule ? place.schedule.earliestTime() : new Date();

        return {
            place: place,
            time: time
        };
    }
};
